package org.crud4j.portlet;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.naming.InitialContext;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.GenericPortlet;
import javax.portlet.PortletConfig;
import javax.portlet.PortletException;
import javax.portlet.PortletRequest;
import javax.portlet.PortletRequestDispatcher;
import javax.portlet.PortletSession;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;

import org.apache.commons.lang.math.RandomUtils;
import org.apache.log4j.Logger;
import org.crud4j.core.CrudBean;
import org.crud4j.core.CrudBrowsePage;
import org.crud4j.core.CrudField;
import org.crud4j.core.CrudService;
import org.crud4j.core.IdInUseException;
import org.crud4j.core.RemoveMarkException;

/**
 * Portlet view for crud service
 */
public class CrudPortlet extends GenericPortlet {

	/**
	 * Log
	 */
	private static Logger log = Logger.getLogger(CrudPortlet.class);

	/**
	 * Browse all
	 */
	private static String BROWSETYPE_ALL = "browsetype.all";

	/**
	 * Browse filter
	 */
	private static String BROWSETYPE_FILTER = "browsetype.filter";

	/**
	 * Crud Service
	 */
	private CrudService service;

	/**
	 * Page size
	 */
	private Integer pageSize;

	/**
	 * {@inheritdoc}
	 */
	@Override
	protected void doView(RenderRequest req, RenderResponse res)
			throws PortletException, IOException {

		Class beanClass = this.getBeanClass(req);

		if (beanClass == null) {
			throw new PortletException("beanClass is undefined");
		}

		// set attribute: crud bean
		req.setAttribute(CrudContextKey.CRUD_SERVICE.getKey(), this.service);

		// set attribute: crud bean
		req.setAttribute(CrudContextKey.EMPTY_BEAN.getKey(), this.service
				.create(this.getBeanClass(req)));

		// set attribute: errors in request
		req.setAttribute(CrudContextKey.ERROR_KEY.getKey(), req
				.getPortletSession().getAttribute(
						CrudContextKey.ERROR_KEY.getKey()));

		// set attribute: current page on request
		Object page = req.getPortletSession().getAttribute(
				CrudContextKey.PAGE_KEY.getKey());
		if (page == null) {
			page = new Integer(0);
			req.getPortletSession().setAttribute(
					CrudContextKey.PAGE_KEY.getKey(), page);
		}
		req.setAttribute(CrudContextKey.PAGE_KEY.getKey(), page);

		// set attribute: bean
		Object bean = req.getPortletSession().getAttribute(
				CrudContextKey.BEAN_KEY.getKey());
		req.setAttribute(CrudContextKey.BEAN_KEY.getKey(), bean);

		// set attribute: beans
		if (req.getPortletSession().getAttribute(
				CrudContextKey.BROWSETYPE_KEY.getKey()) != null
				&& req.getPortletSession().getAttribute(
						CrudContextKey.BROWSETYPE_KEY.getKey()).equals(
						BROWSETYPE_FILTER)) {
			this.doFilteredBrowse(req);
		} else {
			this.doBrowse(req);
		}

		Object beans = req.getPortletSession().getAttribute(
				CrudContextKey.BEANS_KEY.getKey());
		req.setAttribute(CrudContextKey.BEANS_KEY.getKey(), beans);

		// set attribute: more pages
		req.setAttribute(CrudContextKey.MORE_PAGES_KEY.getKey(), req
				.getPortletSession().getAttribute(
						CrudContextKey.MORE_PAGES_KEY.getKey()));

		// prepare response
		res.setContentType("text/html");

		// find action
		CrudAction action = CrudAction.BROWSE;
		String actionName = req
				.getParameter(CrudContextKey.ACTION_KEY.getKey());
		if (actionName != null) {
			action = CrudAction.valueOf(actionName);
		}

		// forward to JSP
		PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(
				"/" + action.getView());
		rd.include(req, res);
	}

	/**
	 * Process persist action
	 * 
	 * @param req
	 * @param res
	 * @throws PortletException
	 * @throws IOException
	 * @throws RemoveMarkException
	 * @throws IdInUseException
	 */
	private void processPersist(ActionRequest req, ActionResponse res)
			throws PortletException, IOException, RemoveMarkException,
			IdInUseException {
		CrudBean bean = service.create(this.getBeanClass(req));

		if (req.getParameter(CrudContextKey.NEW_BEAN_KEY.getKey()) != null) {
			bean.setNewBean(true);
			req.getPortletSession().setAttribute(
					CrudContextKey.BEAN_KEY.getKey(), bean);
		}

		Collection<CrudField> fields = bean.getFields();
		for (CrudField field : fields) {
			// Boolean values are true if present
			if (field.getDef().getType().equals(Boolean.class)) {
				if (req.getParameter(field.getDef().getName()) != null) {
					field.setValue(Boolean.TRUE.toString());
				} else {
					field.setValue(Boolean.FALSE.toString());
				}
			} else {
				String value = req.getParameter(field.getDef().getName());
				field.setValue(value);
			}
		}

		// validate remove mark
		bean = this.service.persist(bean);

		req.getPortletSession().setAttribute(CrudContextKey.BEAN_KEY.getKey(),
				bean);

	}

	/**
	 * {@inheritdoc}
	 */
	@Override
	public void init(PortletConfig conf) throws PortletException {
		// TODO Auto-generated method stub
		super.init(conf);

		String pageSize = conf.getInitParameter(CrudContextKey.PAGE_SIZE_KEY
				.getKey());

		String crudServiceJndi = conf.getInitParameter("crud.service.jndi");

		if (pageSize != null) {
			try {
				this.pageSize = Integer.valueOf(pageSize);
			} catch (Exception e) {
				throw new PortletException("unable to set page size "
						+ pageSize);
			}
		}

		try {
			InitialContext ctx = new InitialContext();
			this.service = (CrudService) ctx.lookup(crudServiceJndi);
		} catch (Exception e) {
			log.error("unable to find CrudService through JNDI "
					+ crudServiceJndi, e);
			throw new PortletException(
					"unable to find CrudService through JNDI "
							+ crudServiceJndi);
		}
	}

	/**
	 * {@inheritdoc}
	 */
	@Override
	public void processAction(ActionRequest req, ActionResponse res)
			throws PortletException, IOException {

		req.getPortletSession().removeAttribute(
				CrudContextKey.ERROR_KEY.getKey());

		// process action
		try {
			this.processViewAction(req, res);
		} catch (Exception e) {
			Throwable cause = e.getCause();
			if (cause != null
					&& cause.getClass().getName().equals(
							"org.jboss.tm.JBossRollbackException")
					&& cause.getCause() != null
					&& cause
							.getCause()
							.getClass()
							.getName()
							.equals(
									"org.hibernate.exception.ConstraintViolationException")) {
				StringBuilder errorMessage = new StringBuilder();
				errorMessage
						.append("Error al intentar guardar. Posiblemente el error se deba ");
				errorMessage
						.append("a que el valor ingresado ya existe o se encuentra en un ");
				errorMessage.append("ingreso ya eliminado de forma logica");
				req.getPortletSession().setAttribute(
						CrudContextKey.ERROR_KEY.getKey(),
						errorMessage.toString());
			} else {
				req.getPortletSession().setAttribute(
						CrudContextKey.ERROR_KEY.getKey(), e.getMessage());
			}

		}

		// FIXME: cache issue, adding a random value as a workaround
		res.setRenderParameter(String.valueOf(RandomUtils.nextInt()), "");
	}

	/**
	 * Process view Action
	 * 
	 * @param req
	 * @param res
	 * @throws PortletException
	 * @throws IOException
	 */
	private void processViewAction(ActionRequest req, ActionResponse res)
			throws PortletException, IOException {

		Class beanClass = this.getBeanClass(req);
		CrudBean emptyCrudBean = this.service.create(beanClass);

		CrudAction action = CrudAction.DEFAULT;

		String actionName = (String) req.getParameter(CrudContextKey.ACTION_KEY
				.getKey());

		if (actionName != null) {
			action = CrudAction.valueOf(actionName);
		}

		// persist
		if (action.equals(CrudAction.PERSIST)) {
			try {
				this.processPersist(req, res);
			} catch (Exception e) {
				req.getPortletSession().setAttribute(
						CrudContextKey.ERROR_KEY.getKey(), e.getMessage());
			}
		}

		// new
		if (action.equals(CrudAction.NEW)) {
			CrudBean bean = service.create(beanClass);
			bean.setNewBean(true);
			req.getPortletSession().setAttribute(
					CrudContextKey.BEAN_KEY.getKey(), bean);
		}

		// remove
		if (action.equals(CrudAction.REMOVE)) {
			Map<String, String> ids = new HashMap<String, String>();
			for (CrudField field : emptyCrudBean.getIds()) {
				ids.put(field.getDef().getName(), req.getParameter(field
						.getDef().getName()));
			}
			CrudBean bean = service.find(beanClass, ids);
			service.remove(bean);
			this.doBrowse(req);
		}

		// edit & view
		if (action.equals(CrudAction.EDIT) || action.equals(CrudAction.VIEW)) {
			Map<String, String> ids = new HashMap<String, String>();
			for (CrudField field : emptyCrudBean.getIds()) {
				ids.put(field.getDef().getName(), req.getParameter(field
						.getDef().getName()));
			}
			CrudBean bean = service.find(beanClass, ids);
			req.getPortletSession().setAttribute(
					CrudContextKey.BEAN_KEY.getKey(), bean);
		}

		// default
		if (action.equals(CrudAction.DEFAULT)) {
			Map filters = (Map) req.getPortletSession().getAttribute(
					CrudContextKey.FILTER_KEY.getKey());
			if (filters != null && filters.size() > 0) {
				this.doFilteredBrowse(req);
			} else {
				this.doBrowse(req);
			}
		}

		// scroll page
		if (action.equals(CrudAction.SCROLL_PAGE)) {
			String page = (String) req.getParameter(CrudContextKey.PAGE_KEY
					.getKey());
			if (page != null) {
				req.getPortletSession()
						.setAttribute(CrudContextKey.PAGE_KEY.getKey(),
								Integer.valueOf(page));
			}
			Map filters = (Map) req.getPortletSession().getAttribute(
					CrudContextKey.FILTER_KEY.getKey());
			if (filters != null && filters.size() > 0) {
				this.doFilteredBrowse(req);
			} else {
				this.doBrowse(req);
			}
		}

		// browse
		if (action.equals(CrudAction.BROWSE)) {
			req.getPortletSession().setAttribute(
					CrudContextKey.PAGE_KEY.getKey(), Integer.valueOf(0));
			this.doBrowse(req);
		}

		// filtered browse
		if (action.equals(CrudAction.FILTERED_BROWSE)) {
			// set page in 0
			req.getPortletSession().setAttribute(
					CrudContextKey.PAGE_KEY.getKey(), new Integer(0));

			// create filters
			Map<String, String> filters = new HashMap<String, String>();
			for (CrudField field : emptyCrudBean.getBrowseFilterFields()) {
				String param = req.getParameter(field.getDef().getName());
				if (param != null && param.length() > 0) {
					filters.put(field.getDef().getName(), param);
				}
			}
			// save filter in session
			req.getPortletSession().setAttribute(
					CrudContextKey.FILTER_KEY.getKey(), filters);

			// process filter
			this.doFilteredBrowse(req);
		}

		// forward action to render
		res.setRenderParameter(CrudContextKey.ACTION_KEY.getKey(), action
				.name());

	}

	/**
	 * Process a browse request
	 * 
	 * @param req
	 * @throws PortletException
	 * @throws IOException
	 */
	private void doBrowse(PortletRequest req) throws PortletException,
			IOException {
		PortletSession session = req.getPortletSession();

		session.setAttribute(CrudContextKey.BROWSETYPE_KEY.getKey(),
				BROWSETYPE_ALL);

		Integer page = (Integer) session.getAttribute(CrudContextKey.PAGE_KEY
				.getKey());

		CrudBrowsePage browsePage = this.service.browse(this.getBeanClass(req),
				this.pageSize, page);

		List beans = browsePage.getBeans();

		session.setAttribute(CrudContextKey.MORE_PAGES_KEY.getKey(),
				!browsePage.isLast());

		session.setAttribute(CrudContextKey.BEANS_KEY.getKey(), beans);
	}

	/**
	 * Process filtered view
	 * 
	 * @param req
	 */
	@SuppressWarnings("unchecked")
	private void doFilteredBrowse(PortletRequest req) {

		PortletSession session = req.getPortletSession();

		session.setAttribute(CrudContextKey.BROWSETYPE_KEY.getKey(),
				BROWSETYPE_FILTER);

		Map filters = (Map) session.getAttribute(CrudContextKey.FILTER_KEY
				.getKey());

		Integer page = (Integer) session.getAttribute(CrudContextKey.PAGE_KEY
				.getKey());

		CrudBrowsePage browsePage = this.service.filteredBrowse(this
				.getBeanClass(req), this.pageSize, page, filters);

		List beans = browsePage.getBeans();

		session.setAttribute(CrudContextKey.MORE_PAGES_KEY.getKey(),
				!browsePage.isLast());

		session.setAttribute(CrudContextKey.BEANS_KEY.getKey(), beans);
	}

	/**
	 * Retrieves the portlet class from the preferences
	 * 
	 * @param req
	 *            the portlet request
	 * @return the bean class that should be managed by this portlet
	 */
	private Class getBeanClass(PortletRequest req) {
		String className = req.getPreferences().getValue("bean-class", "");
		if (className.length() > 0) {
			try {
				return Class.forName(className);
			} catch (ClassNotFoundException e) {
				log.error("unable to get class name ", e);
			}
		}
		return null;
	}
}
